package org.jbake.template;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.lang.LocaleUtils;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.app.ContentStore;
import org.jbake.app.Crawler.Attributes;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import java.io.File;
import java.io.Writer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* <p>A template engine which renders pages using Thymeleaf.</p>
*
* <p>This template engine is not recommended for large sites because the whole model
* is loaded into memory due to Thymeleaf internal limitations.</p>
*
* <p>The default rendering mode is "HTML", but it is possible to use another mode
* for each document type, by adding a key in the configuration, for example:</p>
*
* <code>
* template.feed.thymeleaf.mode=XML
* </code>
*
* @author Cédric Champeau
*/
public class ThymeleafTemplateEngine extends AbstractTemplateEngine {
private final ReentrantLock lock = new ReentrantLock();
private TemplateEngine templateEngine;
private FileTemplateResolver templateResolver;
private String templateMode;
public ThymeleafTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) {
super(config, db, destination, templatesPath);
}
private void initializeTemplateEngine(String mode) {
if (mode.equals(templateMode)) {
return;
}
templateMode = mode;
templateResolver = new FileTemplateResolver();
templateResolver.setPrefix(templatesPath.getAbsolutePath() + File.separatorChar);
templateResolver.setCharacterEncoding(config.getString(Keys.TEMPLATE_ENCODING));
templateResolver.setTemplateMode(mode);
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
}
@Override
public void renderDocument(final Map<String, Object> model, final String templateName, final Writer writer) throws RenderingException {
String localeString = config.getString(Keys.THYMELEAF_LOCALE);
Locale locale = localeString != null ? LocaleUtils.toLocale(localeString) : Locale.getDefault();
Context context = new Context(locale, wrap(model));
lock.lock();
try {
@SuppressWarnings("unchecked")
Map<String, Object> config = (Map<String, Object>) model.get("config");
@SuppressWarnings("unchecked")
Map<String, Object> content = (Map<String, Object>) model.get("content");
String mode = "HTML";
if (config != null && content != null) {
String key = "template_" + content.get(Attributes.TYPE) + "_thymeleaf_mode";
String configMode = (String) config.get(key);
if (configMode != null) {
mode = configMode;
}
}
initializeTemplateEngine(mode);
templateEngine.process(templateName, context, writer);
} finally {
lock.unlock();
}
}
private Map<String, Object> wrap(final Map<String, Object> model) {
return new JBakeMap(model);
}
private class JBakeMap extends HashMap<String, Object> {
public JBakeMap(final Map<String, Object> model) {
super(model);
for(String key : extractors.keySet()) {
try {
put(key, extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter()));
} catch (NoModelExtractorException e) {
// should never happen, as we iterate over existing extractors
}
}
}
}
}